﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BeanboxSoftware.BeanProxy;
using BeanboxSoftware.BeanProxy.Recording;

namespace BeanProxyTest
{
  [TestClass]
  public class SequenceTest
  {
    public abstract class SequenceTestClass
    {
      public abstract void A();
      public abstract int B(decimal i);
      public abstract string C(SequenceTestClass s);
    }

    [TestMethod]
    public void BasicSequenceVerifyTest()
    {
      var proxy = ProxyManager.CreateProxy<SequenceTestClass>();
      var a = ProxyManager.GetAction(proxy.A);
      var b = ProxyManager.GetFunc<decimal, int>(proxy.B);
      var c = ProxyManager.GetFunc<SequenceTestClass, string>(proxy.C);

      ProxyManager.GetFunc<object, bool>(proxy.Equals).IsReal = true;

      a.Access
        .Listen(In.Sequence)
        .For()
        .Then = (va) =>
          { return null; };

      b.Access
        .Listen(In.Sequence)
        .For(1m)
        .Then = (va) =>
        {
          return 4;
        };

      c.Access
        .Listen(In.Sequence)
        .For(proxy)
        .Then = va => "done";

      proxy.A();
      int bResult = proxy.B(1m);
      string cResult = proxy.C(proxy);

      Assert.AreEqual(4, bResult);
      Assert.AreEqual("done", cResult);
      ProxyManager.VerifyCalls(proxy);
    }

    [TestMethod]
    public void BrokenSequenceTest()
    {
      var proxy = ProxyManager.CreateProxy<SequenceTestClass>();
      var a = ProxyManager.GetAction(proxy.A);
      var b = ProxyManager.GetFunc<decimal, int>(proxy.B);
      var c = ProxyManager.GetFunc<SequenceTestClass, string>(proxy.C);

      ProxyManager.GetFunc<object, bool>(proxy.Equals).IsReal = true;

      a.Access
        .Listen(In.Sequence)
        .For()
        .Then = (va) =>
        { return null; };

      b.Access
        .Listen(In.Sequence)
        .For(1m)
        .Then = (va) =>
        {
          return 4;
        };

      TestUtils.AssertExceptionThrown<InvalidOperationException>(() =>
      {
        int bResult = proxy.B(1m);
      });
    }

    public abstract class MyClass
    {
      public abstract void Action();
      public abstract void Action(int i);
      public abstract void Action(int i, string s);
      public abstract void Action(int i, string s, double d);
      public abstract void Action(int i, string s, double d, decimal dec);
      public abstract void Action(int i, string s, double d, decimal dec, byte b);

      public abstract MyClass Func();
      public abstract int Func(int i);
      public abstract string Func(int i, string s);
      public abstract double Func(int i, string s, double d);
      public abstract decimal Func(int i, string s, double d, decimal dec);
      public abstract byte Func(int i, string s, double d, decimal dec, byte b);
    }

    [TestMethod]
    public void RepeatTest()
    {
      var proxy = ProxyManager.CreateProxy<MyClass>();
      var config = ProxyManager.GetAction<int>(proxy.Action);

      int n = 10;

      config.Access.Listen(n, In.Sequence).For(1).Then = va => null;

      for (int i = 0; i < n; i++)
      {
        proxy.Action(1);
      }
    }

    [TestMethod]
    public void StrictTest()
    {
      var proxy = ProxyManager.CreateProxy<MyClass>();

      ProxyManager.AllowExpectedCallsOnly(proxy, true);

      var config = ProxyManager.GetAction<int>(proxy.Action);

      int n = 10;

      config.Access.Listen(n, In.Sequence).For(1).Then = va => null;
      config.Access.Listen(1, In.Sequence).For(2).Then = va => null;
      config.Access.Listen(2, In.AnyOrder).For(3).Then = va => null;

      proxy.Action(3);

      for (int i = 0; i < n; i++)
      {
        proxy.Action(1);
      }

      proxy.Action(3);
      proxy.Action(2);

      TestUtils.AssertExceptionThrown<InvalidOperationException>(() =>
      {
        proxy.Action(1);
      });
    }

    [TestMethod]
    public void StrongTypedListenTest()
    {
      var proxy = ProxyManager.CreateProxy<MyClass>();

      int valuePassed = 0;

      ProxyManager.GetAction<int>(proxy.Action)
        .Listen(In.Sequence, (i) => { valuePassed++; })
        .For(1);

      ProxyManager.GetAction<int, string>(proxy.Action)
        .Listen(In.Sequence, (i, s) => { valuePassed++; })
        .For(1, "s");

      ProxyManager.GetAction<int, string, double>(proxy.Action)
        .Listen(In.Sequence, (i, s, d) => { valuePassed++; })
        .For(1, "s", 1.0);

      ProxyManager.GetAction<int, string, double, decimal>(proxy.Action)
        .Listen(In.Sequence, (i, s, d, dc) => { valuePassed++; })
        .For(1, "s", 1.1, 1m);

      ProxyManager.GetAction<int, string, double, decimal, byte>(proxy.Action)
        .Listen(In.Sequence, (i, s, d, dc, b) => { valuePassed++; })
        .For(1, "s", 1.2, 2m, 0x10);

      proxy.Action(1);
      proxy.Action(1, "s");
      proxy.Action(1, "s", 1.0);
      proxy.Action(1, "s", 1.1, 1m);
      proxy.Action(1, "s", 1.2, 2m, 0x10);
      Assert.AreEqual(5, valuePassed);

      ProxyManager.VerifyCalls(proxy);
    }

    public class DeclaringType<T>
    {
      public virtual Nested<U> GetNested<U>(DeclaringType<T> dt) { throw new NotImplementedException(); }

      public class Nested<V>
      {
        public class Inner<X>
        {
          public virtual DeclaringType<W> GetDeclaringType<W>(
            ref Inner<V> inv,
            out Inner<X> inx,
            Inner<T> i,
            params Nested<T>[] nt) { throw new NotImplementedException(); }
        }
      }
    }

    public delegate DeclaringType<W> GetDeclaringTypeDelegate<W, T, V, X>(
      ref DeclaringType<T>.Nested<V>.Inner<V> inv,
      out DeclaringType<T>.Nested<V>.Inner<X> inx,
      DeclaringType<T>.Nested<V>.Inner<T> i,
      params DeclaringType<T>.Nested<T>[] nt);

    [TestMethod]
    public void NestedStrongTypedSequenceTest()
    {
      var proxy = ProxyManager.CreateProxy
        <DeclaringType<DeclaringType<string>.Nested<double>>.Nested<decimal?>.Inner<bool>>();

      var config = ProxyManager.GetMethod<GetDeclaringTypeDelegate<
        In,
        DeclaringType<string>.Nested<double>,
        decimal?,
        bool
        >>(proxy.GetDeclaringType<In>);

      var a = new DeclaringType<DeclaringType<string>.Nested<double>>.Nested<decimal?>.Inner<decimal?>();
      DeclaringType<DeclaringType<string>.Nested<double>>.Nested<decimal?>.Inner<bool> b;
      var c = new DeclaringType<DeclaringType<string>.Nested<double>>.Nested<decimal?>.
        Inner<DeclaringType<string>.Nested<double>>();

      var p = new DeclaringType<DeclaringType<string>.Nested<double>>.
        Nested<DeclaringType<string>.Nested<double>>[] 
        { 
          new DeclaringType<DeclaringType<string>.Nested<double>>.
            Nested<DeclaringType<string>.Nested<double>>() 
        };

      var expected = new DeclaringType<In>();

      int called = 0;
      config.ListenGeneric(2, In.Sequence, va => { called++; return expected; }).For(ref a, out b, c, p);

      //out should be ignored
      b = new DeclaringType<DeclaringType<string>.Nested<double>>.Nested<decimal?>.Inner<bool>();

      var actual = proxy.GetDeclaringType<In>(ref a, out b, c, p);

      Assert.AreEqual(expected, actual);
      Assert.AreEqual(1, called);
      Assert.AreEqual(1, config.Count);
    }

    public abstract class Ambig
    {
      public abstract int Foo(int i);
    }

    [TestMethod]
    public void ListenFuncAmbiguityWithParamsTest()
    {
      var ambig = ProxyManager.CreateProxy<Ambig>();

      var foo = ProxyManager.GetFunc<int, int>(ambig.Foo);

      foo.Listen(In.Sequence, i => -i).For(-1);

      Assert.AreEqual(1, ambig.Foo(-1));
    }
  }
}
